﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace TCCHWLib
{
    public class CacheMemory : Memory
    {
        private SortedDictionary<int, CacheEntry> m_Buffer = null;
        List<int> m_ModifiedList = null;
        Size m_Capacity;
        private Processor m_Processor;

        public new SortedDictionary<int, CacheEntry> Buffer
        {
            get { return m_Buffer; }
            set { m_Buffer = value; }
        }

        public CacheMemory(Size capacity, Processor processor)
            : base(capacity)
        {
            m_Capacity = capacity;
            m_Buffer = new SortedDictionary<int, CacheEntry>();
            m_ModifiedList = new List<int>();
            m_Processor = processor;
        }
        public override int Allocate(int size)
        {
            lock (m_Buffer)
            {
                int address = Chip.Instance.Bus.Allocate(size);
                int begin = ReturnAddress(address);
                int tag, index;
                int currentaddress = address;
                for (int i = 0; i < size; i++)
                {
                    tag = ReturnTag(currentaddress);
                    index = ReturnAddress(currentaddress);
                    CacheEntry newEntry = new CacheEntry(tag, null, MESI.Exclusive);
                    if (m_Buffer.ContainsKey(index))
                    {
                        m_Buffer.Remove(index);
                    }
                    m_Buffer.Add(index, newEntry);
                    if (size > i + 1)
                        currentaddress++;

                }
                UpdateListView();
                return address;
            }
        }
        public override void  Deallocate(int address, int size)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                int tag = ReturnTag(address);
                if (m_Buffer[index].Address != tag)
                    throw new Exception("address not found");
                Chip.Instance.Bus.Deallocate(address, size);
                for (int i = 0; i < size; i++)
                    m_Buffer.Remove(index + i);
                UpdateListView();
                return;
            }
        }
        public override void Deallocate(int address)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                int tag = ReturnTag(address);
                if (m_Buffer[index].Address != tag)
                    throw new Exception("address not found");
                Chip.Instance.Bus.Deallocate(address, 1);
                if (m_Buffer.ContainsKey(address))
                {
                    m_Buffer.Remove(index);
                }
                UpdateListView();
                return;
            }
        }
        public override object Read(int address)
        {
    //        lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                if (m_Buffer.ContainsKey(index))
                {
                    if (ReturnTag(address) == m_Buffer[index].Address && m_Buffer[index].MESIBits != MESI.WriteSet && m_Buffer[index].MESIBits != MESI.Invalid)
                    {
                        UpdateListView();
                        return m_Buffer[index].Value;
                    }
                    m_Buffer.Remove(index);
                }
                MESI mesiTag = new MESI();
                object value = Chip.Instance.Bus.Read(address, out mesiTag);
                m_Buffer.Add(index, new CacheEntry(ReturnTag(address), value, mesiTag));
                UpdateListView();
                return value;
            }
        }
        public override bool Write(int address,object value)
        {
   //         lock (m_Buffer)
            {
                if (Chip.Instance.Bus.Write(address, value))
                {
                    int index = ReturnAddress(address);
                    if (m_Buffer.ContainsKey(index))
                    {
                        if (ReturnTag(address) == m_Buffer[index].Address)
                        {
                            m_Buffer[index].Value = value;
                            m_Buffer[index].MESIBits = MESI.Modified;
                            m_ModifiedList.Add(index);
                            UpdateListView();
                            return Chip.Instance.Bus.Write(address, value);
                        }
                        m_Buffer.Remove(index);
                    }
                    CacheEntry entry = new CacheEntry(ReturnTag(address), value, MESI.Modified);
                    m_Buffer.Add(index, entry);
                    m_ModifiedList.Add(index);
                    UpdateListView(m_Buffer);
                    return true;
                }
                return false;
                
            }
        }
        public List<int> ReturnModified()
        {
            return null;

        }
        public void Clear()
        {
            m_Buffer.Clear();
        }
        #region helperFunction
        /// <summary>
        /// Calculate the value of which block this address falls if the shared memory is divided into blocks of cache size
        /// </summary>
        /// <param name="address">address from the shared memory</param>
        /// <returns></returns>
        int ReturnTag(int address)
        {
            return (int)Math.Floor((double)address / (double)m_Capacity.Height);
        }
        /// <summary>
        /// Calculate the value of the index which is the mode of address divided the capacity assuming that the width is 1
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        int ReturnAddress(int address)
        {
            return address % m_Capacity.Height;
        }

        public override string ToString()
        {
            string memory = "";
            foreach (int i in m_Buffer.Keys)
            {
                memory += i.ToString();
                memory += "---";
                memory += m_Buffer[i].ToString();
                memory += "\n";
            }
            return memory;
        }
        #endregion
        #region BusFunctions
        /// <summary>
        /// notify the bus if it contains the address
        /// </summary>
        /// <param name="addess">adddress inspected</param>
        /// <returns></returns>
        public bool ContainValue(int addess)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(addess);
                if (m_Buffer.ContainsKey(index) && m_Buffer[index].Address == ReturnTag(addess))
                    if (m_Buffer[index].MESIBits != MESI.WriteSet)
                        return true;
                    
            }
            return false;
        }
        public void MesiShared(int address)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                m_Buffer[index].MESIBits = MESI.Shared;
            }
            UpdateListView();
        }
        public void MesiInvalid(int address)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                m_Buffer[index].MESIBits = MESI.Invalid;
            }
            UpdateListView(); 
        }
        public void MesiModified(int address)
        {
            lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                m_Buffer[index].MESIBits = MESI.Modified;
                m_ModifiedList.Add(index);
            }
            UpdateListView();
        }
        #endregion

        private delegate void UiUpdate();
        public override void UpdateListView()
        {
            if (m_UiSharedMemory.InvokeRequired)
            {
                m_UiSharedMemory.BeginInvoke(new UiUpdate(UpdateListView));
                return;
            }

            List<ListViewItem> items = new List<ListViewItem>();
            ListViewItem item;

            foreach (KeyValuePair<int, CacheEntry> pair in m_Buffer)
            {
                if (m_Processor.WriteSet.Buffer.ContainsKey(pair.Key))
                {
                    CacheEntry e = new CacheEntry(pair.Key, m_Processor.WriteSet.Buffer[pair.Key]);
                    e.MESIBits = pair.Value.MESIBits;
                    item = new ListViewItem(e.ToString()); // Content
                }
                else
                    item = new ListViewItem(pair.Value.ToString()); // Content

                item.Tag = pair.Key; // Address
                items.Add(item);
            }
            m_UiSharedMemory.UpdateMemory(items);
        }

        public void ReadWriteSet(int address)
        {
    //        lock (m_Buffer)
            {
                int index = ReturnAddress(address);
                if (m_Buffer.ContainsKey(index))
                {
                    if (ReturnTag(address) == m_Buffer[index].Address && m_Buffer[index].MESIBits != MESI.Invalid)
                    {
                        m_Buffer[index].MESIBits = MESI.Modified;
                        UpdateListView();
                        return;
                    }
                    m_Buffer.Remove(index);
                }
                MESI mesiTag = new MESI();
                object value = Chip.Instance.Bus.Read(address, out mesiTag);
                mesiTag = MESI.WriteSet;
                m_Buffer.Add(index, new CacheEntry(ReturnTag(address), value, mesiTag));
                UpdateListView();
                return;
            }
        }

        public void ClearWriteSet()
        {
            List<int> addresses = new List<int>();
            List<int> addresRead = new List<int>();
  //          lock (m_Buffer)
            {
                foreach (int address in m_Buffer.Keys)
                {
                    if (m_Buffer[address].MESIBits == MESI.WriteSet)
                        addresses.Add(address);
                    if (m_Buffer[address].MESIBits == MESI.Modified && m_ModifiedList.Contains(address))
                        addresRead.Add(address);
                }
                foreach (int address in addresses)
                {
                    m_Buffer.Remove(address);
                }
                foreach (int address in addresRead)
                {
                    Read(address);
                }
            }
        }

        public void ClearRest()
        {
            foreach (int address in m_Buffer.Keys)
            {
                m_Buffer[address].MESIBits = MESI.Invalid;
            }
        }
    }
}
